package com.hys.app.service.erp.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hys.app.converter.erp.WarehouseOutConverter;
import com.hys.app.converter.erp.WarehouseOutItemConverter;
import com.hys.app.framework.database.WebPage;
import com.hys.app.framework.database.mybatisplus.base.BaseServiceImpl;
import com.hys.app.framework.exception.ServiceException;
import com.hys.app.framework.rabbitmq.MessageSender;
import com.hys.app.framework.rabbitmq.MqMessage;
import com.hys.app.framework.util.DateUtil;
import com.hys.app.framework.util.PageConvert;
import com.hys.app.framework.util.StringUtil;
import com.hys.app.mapper.erp.WarehouseOutMapper;
import com.hys.app.model.base.rabbitmq.AmqpExchange;
import com.hys.app.model.erp.dos.*;
import com.hys.app.model.erp.dto.*;
import com.hys.app.model.erp.dto.message.WarehouseOutShipMessage;
import com.hys.app.model.erp.enums.*;
import com.hys.app.model.erp.vo.OrderAllowable;
import com.hys.app.model.erp.vo.WarehouseOutAllowable;
import com.hys.app.model.erp.vo.WarehouseOutStatistics;
import com.hys.app.model.erp.vo.WarehouseOutVO;
import com.hys.app.model.system.dos.AdminUser;
import com.hys.app.model.system.dos.LogisticsCompanyDO;
import com.hys.app.service.erp.*;
import com.hys.app.service.system.AdminUserManager;
import com.hys.app.service.system.LogisticsCompanyManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.hys.app.framework.util.CollectionUtils.*;

/**
 * 出库单业务层实现
 *
 * @author 张崧
 * @since 2023-12-07 16:50:20
 */
@Service
public class WarehouseOutManagerImpl extends BaseServiceImpl<WarehouseOutMapper, WarehouseOutDO> implements WarehouseOutManager {

    @Autowired
    private WarehouseOutConverter converter;

    @Autowired
    private WarehouseOutItemConverter itemConverter;

    @Autowired
    private OrderManager orderManager;

    @Autowired
    private NoGenerateManager noGenerateManager;

    @Autowired
    private WarehouseOutItemManager warehouseOutItemManager;

    @Autowired
    private WarehouseEntryBatchManager batchManager;

    @Autowired
    private LogisticsCompanyManager logisticsCompanyManager;

    @Autowired
    private WarehouseOutMapper warehouseOutMapper;

    @Autowired
    private MessageSender messageSender;

    @Autowired
    private AdminUserManager adminUserManager;

    @Autowired
    private OrderItemManager orderItemManager;

    @Autowired
    private FinanceItemManager financeItemManager;

    @Override
    public WebPage<WarehouseOutVO> list(WarehouseOutQueryParams queryParams) {
        WebPage<WarehouseOutDO> webPage = baseMapper.selectPage(queryParams);
        return converter.convert(webPage);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void add(WarehouseOutDTO warehouseOutDTO) {
        // 校验参数
        checkAddOrEdit(warehouseOutDTO);

        // 保存出库单
        WarehouseOutDO warehouseOutDO = converter.combination(warehouseOutDTO);
        warehouseOutDO.setSn(noGenerateManager.generate(NoBusinessTypeEnum.WarehouseOut, warehouseOutDO.getDeptId()));
        warehouseOutDO.setStatus(WarehouseOutStatusEnum.WAIT_AUDIT);
        warehouseOutDO.setCreateBy(adminUserManager.getCurrUserName());
        save(warehouseOutDO);

        // 保存出库单明细
        warehouseOutDTO.setId(warehouseOutDO.getId());
        List<WarehouseOutItemDO> productList = itemConverter.combination(warehouseOutDTO);
        warehouseOutItemManager.saveBatch(productList);

        // 将订单改为已出库状态
        orderManager.warehouseOut(warehouseOutDO.getOrderIdList(), warehouseOutDO.getId());
    }

    @Override
    public void edit(WarehouseOutDTO warehouseOutDTO) {
        WarehouseOutDO old = getById(warehouseOutDTO.getId());
        if (!new WarehouseOutAllowable(old).getEdit()) {
            throw new ServiceException("当前状态不允许进行编辑操作");
        }

        if (!convertSet(warehouseOutDTO.getOrderList(), WarehouseOutDTO.Order::getOrderId).equals(new HashSet<>(old.getOrderIdList()))) {
            throw new ServiceException("订单和出库单已有订单不一致");
        }

        // 校验参数
        checkAddOrEdit(warehouseOutDTO);

        // 保存出库单
        WarehouseOutDO warehouseOutDO = converter.combination(warehouseOutDTO);
        updateById(warehouseOutDO);

        // 保存出库单明细
        List<WarehouseOutItemDO> productList = itemConverter.combination(warehouseOutDTO);
        warehouseOutItemManager.deleteByWarehouseOutId(Collections.singletonList(warehouseOutDTO.getId()));
        warehouseOutItemManager.saveBatch(productList);
    }

    @Override
    public WarehouseOutVO getDetail(Long id) {
        WarehouseOutDO warehouseOutDO = getById(id);

        // 查询出库明细
        List<WarehouseOutItemDO> itemList = warehouseOutItemManager.listByWarehouseOutId(id);
        // 查询批次信息（回显批次剩余库存）
        List<WarehouseEntryBatchDO> batchList = batchManager.listByIds(convertList(itemList, WarehouseOutItemDO::getWarehouseEntryBatchId));

        return converter.convert(warehouseOutDO, itemList, batchList);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(List<Long> ids) {
        List<WarehouseOutDO> warehouseOutList = listByIds(ids);
        for (WarehouseOutDO warehouseOutDO : warehouseOutList) {
            if (!new WarehouseOutAllowable(warehouseOutDO).getDelete()) {
                throw new ServiceException(warehouseOutDO.getSn() + "不能进行删除操作");
            }
        }

        // 删除
        removeBatchByIds(ids);
        // 删除明细
        warehouseOutItemManager.deleteByWarehouseOutId(ids);
        // 订单改为未出库
        orderManager.warehouseOutDelete(ids);
    }

    @Override
    public WarehouseOutVO preview(WarehouseOutPreviewDTO warehouseOutPreviewDTO) {
        // 校验订单编号是否正确
        List<OrderDO> orderList = checkOrderData(warehouseOutPreviewDTO.getOrderIdList());

        // 查询订单明细
        List<OrderItemDO> orderItemList = orderItemManager.listByOrderIds(warehouseOutPreviewDTO.getOrderIdList());

        // 查询这些商品的可用入库批次
        List<Long> productIds = convertList(orderItemList, OrderItemDO::getProductId);
        Map<Long, List<WarehouseEntryBatchDO>> warehouseBatchMap = batchManager.listAvailableBatch(orderList.get(0).getWarehouseId(), productIds);

        return converter.convertPreview(orderList, orderItemList, warehouseBatchMap);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void ship(WarehouseOutShipDTO shipDTO) {
        Long id = shipDTO.getId();
        WarehouseOutDO warehouseOutDO = getById(id);
        if (!new WarehouseOutAllowable(warehouseOutDO).getShip()) {
            throw new ServiceException("当前状态不能进行发货操作");
        }

        // 校验参数并填充数据到shipDTO
        checkShipParams(warehouseOutDO.getDeliveryType(), shipDTO);

        // 扣减批次库存
        List<WarehouseOutItemDO> productList = warehouseOutItemManager.listByWarehouseOutId(id);
        List<StockUpdateDTO> stockUpdateList = converter.convertStockUpdateList(productList);
        batchManager.updateStock(StockChangeSourceEnum.WAREHOUSE_OUT, warehouseOutDO.getSn(), stockUpdateList);

        // 更新出库单信息
        lambdaUpdate()
                .set(WarehouseOutDO::getStatus, WarehouseOutStatusEnum.SHIP)
                .set(WarehouseOutDO::getShipTime, DateUtil.getDateline())
                .set(WarehouseOutDO::getLogisticsCompanyId, shipDTO.getLogisticsCompanyId())
                .set(WarehouseOutDO::getLogisticsCompanyName, shipDTO.getLogisticsCompanyName())
                .set(WarehouseOutDO::getTrackingNumber, shipDTO.getTrackingNumber())
                .set(WarehouseOutDO::getFreightPrice, shipDTO.getFreightPrice())
                .set(WarehouseOutDO::getConsignee, shipDTO.getConsignee())
                .eq(WarehouseOutDO::getId, id)
                .update();

        // 将订单状态更新为已发货
        orderManager.warehouseOutShip(shipDTO);

        // 生成财务明细
        financeItemManager.addExpand(FinanceExpandTypeEnum.Logistics, warehouseOutDO.getSn(), shipDTO.getFreightPrice());

        // 发送出库单发货消息
        WarehouseOutShipMessage message = new WarehouseOutShipMessage();
        message.setId(id);
        message.setItemList(productList);
        this.messageSender.send(new MqMessage(AmqpExchange.WAREHOUSE_OUT_SHIP, AmqpExchange.WAREHOUSE_OUT_SHIP + "_ROUTING", message));
    }

    /**
     * 查询出库单统计分页列表数据
     *
     * @param params 查询参数
     * @return
     */
    @Override
    public WebPage statistics(WarehouseOutStatisticsParam params) {
        IPage<WarehouseOutStatistics> iPage = this.warehouseOutMapper.selectWarehouseOutPage(new Page(params.getPageNo(), params.getPageSize()), params);
        return PageConvert.convert(iPage);
    }

    /**
     * 导出出库单统计列表
     *
     * @param response
     * @param params   查询参数
     */
    @Override
    public void export(HttpServletResponse response, WarehouseOutStatisticsParam params) {
        //查询出库单商品列表
        List<WarehouseOutStatistics> list = this.warehouseOutMapper.selectWarehouseOutList(params);

        ArrayList<Map<String, Object>> rows = CollUtil.newArrayList();
        ExcelWriter writer = ExcelUtil.getWriter(true);
        for (WarehouseOutStatistics statistics : list) {
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("所属部门", statistics.getDeptName());
            map.put("仓库名称", statistics.getWarehouseName());
            map.put("服务专员", statistics.getDistributionName());
            map.put("出库单编号", statistics.getSn());
            map.put("出库时间", DateUtil.toString(statistics.getOutTime(), "yyyy-MM-dd HH:mm:ss"));
            map.put("入库单编号", statistics.getWarehouseEntrySn());
            map.put("商品编号", statistics.getProductSn());
            map.put("商品名称", statistics.getProductName());
            map.put("商品类别", statistics.getCategoryName());
            map.put("规格型号", statistics.getProductSpecification());
            map.put("单位", statistics.getProductUnit());
            map.put("出库数量", statistics.getOutNum());
            rows.add(map);
        }

        writer.write(rows, true);

        ServletOutputStream out = null;
        try {
            out = response.getOutputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
        response.setHeader("Content-Disposition", "attachment;filename=warehouse_out.xlsx");
        writer.flush(out, true);
        writer.close();
        IoUtil.close(out);
    }

    @Override
    public long countByWarehouseId(Long warehouseId) {
        return lambdaQuery().eq(WarehouseOutDO::getWarehouseId, warehouseId).count();
    }

    @Override
    public void audit(List<Long> ids, WarehouseOutStatusEnum status, String remark) {
        if (status != WarehouseOutStatusEnum.AUDIT_PASS && status != WarehouseOutStatusEnum.AUDIT_REJECT) {
            throw new ServiceException("审核参数错误");
        }

        List<WarehouseOutDO> list = listByIds(ids);
        for (WarehouseOutDO warehouseOutDO : list) {
            if (!new WarehouseOutAllowable(warehouseOutDO).getAudit()) {
                throw new ServiceException(warehouseOutDO.getSn() + "不能进行审核操作");
            }
        }

        AdminUser currUser = adminUserManager.getCurrUser();
        lambdaUpdate()
                .set(WarehouseOutDO::getStatus, status)
                .set(WarehouseOutDO::getAuditById, currUser.getId())
                .set(WarehouseOutDO::getAuditByName, currUser.getRealName())
                .in(WarehouseOutDO::getId, ids)
                .update();
    }

    private void checkAddOrEdit(WarehouseOutDTO warehouseOutDTO) {
        List<WarehouseOutDTO.Order> orderDTOList = warehouseOutDTO.getOrderList();
        // 1. 校验订单
        List<Long> orderIds = convertList(orderDTOList, WarehouseOutDTO.Order::getOrderId);
        List<OrderDO> orderList = checkOrderData(orderIds);
        warehouseOutDTO.setOrderMap(convertMap(orderList, OrderDO::getId, Function.identity()));

        // 2. 校验订单明细和批次
        // 2.1 查询订单项
        List<Long> orderItemIds = orderDTOList.stream().flatMap(orderDTO -> orderDTO.getOrderItemList().stream())
                .map(WarehouseOutDTO.OrderItem::getOrderItemId).collect(Collectors.toList());
        Map<Long, OrderItemDO> orderItemMap = orderItemManager.listAndConvertMap(orderItemIds, OrderItemDO::getId);
        warehouseOutDTO.setOrderItemMap(orderItemMap);
        // 2.2 查询批次
        List<Long> batchIds = orderDTOList.stream()
                .flatMap(orderDTO -> orderDTO.getOrderItemList().stream().flatMap(orderItemDTO -> orderItemDTO.getBatchList().stream()))
                .map(WarehouseOutDTO.StockBatch::getBatchId).collect(Collectors.toList());
        Map<Long, WarehouseEntryBatchDO> batchMap = batchManager.listAndConvertMap(batchIds, WarehouseEntryBatchDO::getId);
        warehouseOutDTO.setBatchMap(batchMap);
        // 2.3 循环校验订单明细和批次
        for (WarehouseOutDTO.Order order : orderDTOList) {
            for (WarehouseOutDTO.OrderItem orderItem : order.getOrderItemList()) {

                // 校验订单明细
                OrderItemDO orderItemDO = orderItemMap.get(orderItem.getOrderItemId());
                if (orderItemDO == null) {
                    throw new ServiceException(StrUtil.format("订单项【{}】不存在", orderItem.getOrderItemId()));
                }
                if (!orderItemDO.getOrderId().equals(order.getOrderId())) {
                    throw new ServiceException(StrUtil.format("订单项【{}】不属于订单【{}】", orderItem.getOrderItemId(), order.getOrderId()));
                }

                // 校验批次
                for (WarehouseOutDTO.StockBatch stockBatch : orderItem.getBatchList()) {
                    WarehouseEntryBatchDO batchDO = batchMap.get(stockBatch.getBatchId());
                    if (batchDO == null) {
                        throw new ServiceException(StrUtil.format("批次【{}】不存在", stockBatch.getBatchId()));
                    }
                    if (!batchDO.getProductId().equals(orderItemDO.getProductId())) {
                        throw new ServiceException(StrUtil.format("批次【{}】不属于商品【{}】", batchDO.getId(), orderItemDO.getProductId()));
                    }
                    if (!batchDO.getWarehouseId().equals(orderList.get(0).getWarehouseId())) {
                        throw new ServiceException(StrUtil.format("批次【{}】不属于仓库【{}】", batchDO.getId(), orderList.get(0).getWarehouseId()));
                    }
                    // 这里先前置校验一下，后续发货时会真正校验库存
                    if (batchDO.getRemainNum() < stockBatch.getOutNum()) {
                        throw new ServiceException(StrUtil.format("批次【{}】的剩余库存数不足", batchDO.getSn()));
                    }

                }

                // 校验orderItem的销售数量和批次的出库数量是否一致
                if (orderItemDO.getNum() != orderItem.getBatchList().stream().mapToInt(WarehouseOutDTO.StockBatch::getOutNum).sum()) {
                    throw new ServiceException(StrUtil.format("商品【{}】的销售数量和出库数量不一致", orderItemDO.getProductName()));
                }
            }
        }

        // 仓库提货人
        AdminUser warehouseConsignee = adminUserManager.getModel(warehouseOutDTO.getWarehouseConsigneeId());
        if (warehouseConsignee == null) {
            throw new ServiceException("仓库提货人不存在");
        }
        warehouseOutDTO.setWarehouseConsignee(warehouseConsignee);

    }

    private void checkShipParams(OrderDeliveryType deliveryType, WarehouseOutShipDTO shipDTO) {
        if (deliveryType == OrderDeliveryType.express) {
            if (shipDTO.getLogisticsCompanyId() == null) {
                throw new ServiceException("请选择物流公司");
            }
            if (StringUtil.isEmpty(shipDTO.getTrackingNumber())) {
                throw new ServiceException("物流单号不能为空");
            }
            LogisticsCompanyDO logisticsCompanyDO = logisticsCompanyManager.getModel(shipDTO.getLogisticsCompanyId());
            if (logisticsCompanyDO == null) {
                throw new ServiceException("物流公司不存在");
            }
            shipDTO.setLogisticsCompanyName(logisticsCompanyDO.getName());
        }

    }

    private List<OrderDO> checkOrderData(List<Long> orderIds) {
        // 校验订单存在
        List<OrderDO> orderList = orderManager.listByIds(orderIds);
        Map<Long, OrderDO> orderMap = convertMap(orderList, OrderDO::getId, Function.identity());
        for (Long orderId : orderIds) {
            OrderDO orderDO = orderMap.get(orderId);
            if (orderDO == null) {
                throw new ServiceException(StrUtil.format("订单【{}】不存在", orderId));
            }
            if (!new OrderAllowable(orderDO).getWarehouseOut()) {
                throw new ServiceException(StrUtil.format("订单【{}】不允许进行出库操作", orderDO.getSn()));
            }
        }

        // 校验是否是同一来源的订单
        Map<String, List<OrderDO>> groupBySource = orderList.stream().collect(Collectors.groupingBy(OrderDO::getSourceId));
        if (groupBySource.size() != 1) {
            throw new ServiceException("只能选择同一来源的订单");
        }

        if (orderList.get(0).getSource() == OrderSourceEnum.Internal) {
            // 校验是否是同一仓库的订单
            Map<Long, List<OrderDO>> groupByWarehouse = orderList.stream().collect(Collectors.groupingBy(OrderDO::getWarehouseId));
            if (groupByWarehouse.size() != 1) {
                throw new ServiceException("只能选择同一个仓库的订单");
            }

            // 校验是否是同一会员的订单
            Map<String, List<OrderDO>> groupByMember = orderList.stream().collect(Collectors.groupingBy(OrderDO::getOwnerId));
            if (groupByMember.size() != 1) {
                throw new ServiceException("只能选择同一个用户的订单");
            }
        }

        // 校验所有订单的配送方式是否相同
        Map<OrderDeliveryType, List<OrderDO>> groupByShippingType = orderList.stream().collect(Collectors.groupingBy(OrderDO::getDeliveryType));
        if (groupByShippingType.size() != 1) {
            throw new ServiceException("不能同时选择物流和自提订单");
        }

//        // 如果是物流的话，校验是否是同一收货地址
//        if (OrderDeliveryType.express.name().equals(orderList.get(0).getShippingType())) {
//            Map<String, List<OrderDO>> groupByAddress = orderList.stream().collect(Collectors.groupingBy(orderDO -> orderDO.getShipCountyId() + "_" + orderDO.getShipTownId()));
//            if (groupByAddress.size() != 1) {
//                throw new ServiceException("只能选择同一收货地址的订单");
//            }
//        }

        // 如果是自提的话，校验是否是同一门店
        if (orderList.get(0).getDeliveryType() == OrderDeliveryType.self_pick) {
            Map<Long, List<OrderDO>> groupByBranch = orderList.stream().collect(Collectors.groupingBy(OrderDO::getStoreId));
            if (groupByBranch.size() != 1) {
                throw new ServiceException("只能选择同一自提门店的订单");
            }
        }

        return orderList;
    }

}

